﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Provider.YahooStock;
using Moq;
using System.Reactive.Subjects;
using Provider.Adapter.Mapping;

namespace Provider.Tests
{
    [TestClass]
    public class ProviderTests
    {
        private IDisposable _disposable2;
        private IDisposable _disposable1;
        private Mock<ISubscriberComponent<IKey, IValue>> _mockSubscriber;
        private Mock<IKey> _mockKey;
        private Mock<IPublisherComponent<IKey, IValue>> _mockPublisher;
        private Subject<KeyValuePair<IKey, IValue>> _subscriberUpdatesTrigger;
        [TestInitialize]
        public void SetUp()
        {
            _subscriberUpdatesTrigger = new Subject<KeyValuePair<IKey, IValue>>();
            _mockKey = new Mock<IKey>();
            _mockPublisher = new Mock<IPublisherComponent<IKey, IValue>>();
            _mockSubscriber = new Mock<ISubscriberComponent<IKey, IValue>>();
            _mockSubscriber.Setup(x => x.Updates).Returns(() => _subscriberUpdatesTrigger);
        }

        [TestMethod]
        public void ProviderPublishCallsPublisher()
        {
            var mockSubscriber = new Mock<IRefCountSubscriber<IKey, IValue>>();

            var mockValue = new Mock<IValue>();
            var provider = new Provider<IKey, IValue>(
                mockSubscriber.Object,
                _mockPublisher.Object);

            provider.Publish(_mockKey.Object, mockValue.Object);
            _mockPublisher.Verify(x => x.Publish(_mockKey.Object, mockValue.Object));
        }

        [TestMethod]
        public void ProviderGetUpdatesCallsSubscriberSubscribe()
        {
            var mockSubscriber = new Mock<IRefCountSubscriber<IKey, IValue>>();
            
            var mockValue = new Mock<IValue>();
            var provider = new Provider<IKey, IValue>(
                mockSubscriber.Object,
                _mockPublisher.Object);

            provider.GetUpdates(_mockKey.Object);
            mockSubscriber.Verify(x => x.GetUpdates(_mockKey.Object));
        }

        [TestMethod]
        public void CheckToRefCountSubscriberExtension()
        {
            var refCountSubscriber = _mockSubscriber.Object.ToRefCountSubscriber();
            Assert.AreEqual(refCountSubscriber.GetType(), typeof(RefCountSubscriber<IKey,IValue>));
        }

        [TestMethod]
        public void RefCountSubscriberGetUpdatesCallsGetInitialValue() 
        {
            RefCountSubscriber<IKey, IValue> refCountSubscriber = new RefCountSubscriber<IKey, IValue>(_mockSubscriber.Object);
            var disposable = refCountSubscriber.GetUpdates(_mockKey.Object).Subscribe();
            _mockSubscriber.Verify(x => x.GetIntialValue(_mockKey.Object));
        }

        [TestMethod]
        public void RefCountSubscriberGetUpdatesCallsSubscribe()
        {
            RefCountSubscriber<IKey, IValue> refCountSubscriber = new RefCountSubscriber<IKey, IValue>(_mockSubscriber.Object);
            var disposable = refCountSubscriber.GetUpdates(_mockKey.Object).Subscribe();
            _mockSubscriber.Verify(x => x.Subscribe(_mockKey.Object));
           
        }

        [TestMethod]
        public void RefCountSubscriberUpdatesOnSubscribeComponentComeThroughToCorrectStream()
        {
            int updateCounter1 =0;
            int updateCounter2 = 0;
            var mockkey2 = new Mock<IKey>();
            RefCountSubscriber<IKey, IValue> refCountSubscriber = new RefCountSubscriber<IKey, IValue>(_mockSubscriber.Object);

            _disposable1 = refCountSubscriber.GetUpdates(_mockKey.Object).Subscribe(x => updateCounter1++);
            _disposable2 = refCountSubscriber.GetUpdates(mockkey2.Object).Subscribe(x => updateCounter2++);

            _subscriberUpdatesTrigger.OnNext(new KeyValuePair<IKey, IValue>(_mockKey.Object, new Mock<IValue>().Object));
            Assert.AreEqual(2,updateCounter1);
            Assert.AreEqual(1, updateCounter2);
        }
        

        [TestMethod]
        public void RefCountSubscriberSubscribesOnlyOnceForMultipleGetUpdatesWithSameKey()
        {
            RefCountSubscriber<IKey, IValue> refCountSubscriber = new RefCountSubscriber<IKey, IValue>(_mockSubscriber.Object);
            _disposable1 = refCountSubscriber.GetUpdates(_mockKey.Object).Subscribe();
            _disposable2 = refCountSubscriber.GetUpdates(_mockKey.Object).Subscribe();
            _mockSubscriber.Verify(x => x.Subscribe(_mockKey.Object),Times.Once());
        }

        [TestMethod]
        public void RefCountSubscriberUnSubscribesOnlyOnceForMultipleDisposesWithSameKey()
        {
            RefCountSubscriber<IKey, IValue> refCountSubscriber = new RefCountSubscriber<IKey, IValue>(_mockSubscriber.Object);

            var observable1 = refCountSubscriber.GetUpdates(_mockKey.Object);
            var observable2 = refCountSubscriber.GetUpdates(_mockKey.Object);
            _disposable1 = observable1.Subscribe();
            _disposable2 = observable2.Subscribe();
            _mockSubscriber.Verify(x => x.UnSubscribe(_mockKey.Object), Times.Never());

            _disposable1.Dispose();

            _mockSubscriber.Verify(x => x.UnSubscribe(_mockKey.Object), Times.Never());

            _disposable2.Dispose();

            _mockSubscriber.Verify(x => x.UnSubscribe(_mockKey.Object), Times.Once());
        }


        [TestMethod]
        public void AdapterCheck()
        {
            var mockSubscriber = new Mock<IRefCountSubscriber<IKey, IValue>>();
            mockSubscriber
                .Setup(x => x.GetUpdates(It.IsAny<IEnumerable<IKey>>()))
                .Returns(_subscriberUpdatesTrigger);

            var mapping1 = new Mock<IMapping<IKey, IValue, IKey, IValue>>();
            var targetKey = new Mock<IKey>();
            var targetValue = new Mock<IValue>();
            var sourceValue = new Mock<IValue>();
            
            mapping1.Setup(x => x.SourceKey).Returns(_mockKey.Object);
            mapping1.Setup(x => x.TargetKey).Returns(targetKey.Object);
            mapping1.Setup(x=> x.GetTargetValue(It.IsAny<IValue>())).Returns(targetValue.Object);

            var mockValue = new Mock<IValue>();
            var adapter = new Adapter.Adapter<IKey, IValue, IKey, IValue>(
                mockSubscriber.Object,
                _mockPublisher.Object,
                new [] { mapping1.Object});


            _subscriberUpdatesTrigger.OnNext(new KeyValuePair<IKey, IValue>(_mockKey.Object, sourceValue.Object));

            _mockPublisher.Verify(x => x.Publish(targetKey.Object, targetValue.Object));
        }
    }
}
